mod req;

use crate::core::entity::whitelist::Whitelist;
use crate::core::model::native_version_setting::NativeVersionSetting;
use crate::custom_filter::{CustomFilter, HotUpdateSettings, WhitelistSettings};
use crate::error::{IntoWithStatus, ToStatusCode};
use crate::nested_map::NestedMap;
use crate::router::hot_update_setting_router::req::{
    PostHotUpdateSettingRequest, PutResourceUrlRequest, PutResourceVersionRequest,
};
use share::core::channel::Channel;
use share::core::native_version::NativeVersion;
use snafu::{OptionExt, ResultExt};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing_subscriber::filter::FilterExt;
use warp::http::StatusCode;
use warp::{Filter, Rejection, Reply};

#[derive(Debug, snafu::Snafu)]
pub enum Error {
    #[snafu(display("Failed to get hot update setting. -> {source}"))]
    NotFound { source: crate::nested_map::Error },
    #[snafu(display("Hot update setting already exits. -> {source}"))]
    AlreadyExist { source: crate::nested_map::Error },
}

impl ToStatusCode for Error {
    fn status_code(&self) -> StatusCode {
        match self {
            Error::NotFound { .. } => StatusCode::NOT_FOUND,
            Error::AlreadyExist { .. } => StatusCode::NOT_FOUND,
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

pub struct Implementation {}

impl Implementation {
    async fn post(
        req: PostHotUpdateSettingRequest,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let setting = NativeVersionSetting {
            resource_version: req.resource_version,
            resource_url: req.resource_url,
        };
        hot_update_settings
            .insert(&req.channel, &req.native_version, setting)
            .await
            .context(AlreadyExistSnafu {})?;
        whitelist_settings
            .insert(&req.channel, &req.native_version, Whitelist::new())
            .await
            .context(AlreadyExistSnafu {})?;

        Ok(())
    }

    async fn delete(
        channel: Channel,
        native_version: NativeVersion,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let _ = hot_update_settings.delete(&channel, &native_version).await;
        let _ = whitelist_settings.delete(&channel, &native_version).await;

        Ok(())
    }

    async fn put_resource_version(
        req: PutResourceVersionRequest,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
    ) -> Result<()> {
        hot_update_settings
            .update(&req.channel, &req.native_version, |setting| {
                setting.resource_version = req.resource_version
            })
            .await
            .context(NotFoundSnafu)?;

        Ok(())
    }

    async fn put_resource_url(
        req: PutResourceUrlRequest,
        hot_update_settings: Arc<RwLock<HotUpdateSettings>>,
    ) -> Result<()> {
        hot_update_settings
            .update(&req.channel, &req.native_version, |setting| {
                setting.resource_url = req.url
            })
            .await
            .context(NotFoundSnafu)?;

        Ok(())
    }
}

struct Router {}

impl Router {
    fn post(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(move |req: PostHotUpdateSettingRequest, hot_update_settings, whitelist_settings| async {
                Implementation::post(req, hot_update_settings, whitelist_settings)
                    .await.
                    into_with_status()
            })
    }

    fn delete(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.hot_update_settings())
            .and(filter.whitelist_settings())
            .then(
                |channel, native_version, hot_update_settings, whitelist_settings| async move {
                    Implementation::delete(
                        channel,
                        native_version,
                        hot_update_settings,
                        whitelist_settings,
                    )
                    .await
                    .into_with_status()
                },
            )
    }

    fn put_resource_version(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::put()
            .and(warp::path::path("resource_version"))
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.hot_update_settings())
            .then(|req, hot_update_settings| async move {
                Implementation::put_resource_version(req, hot_update_settings)
                    .await
                    .into_with_status()
            })
    }

    fn put_resource_url(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::put()
            .and(warp::path::path("resource_url"))
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.hot_update_settings())
            .then(|req, hot_update_settings| async move {
                Implementation::put_resource_url(req, hot_update_settings)
                    .await
                    .into_with_status()
            })
    }
}

pub fn get_all_router(
    filter: Arc<CustomFilter>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path::path("hot_update_setting").and(
        Router::post(filter.clone())
            .or(Router::delete(filter.clone()))
            .or(Router::put_resource_version(filter.clone()))
            .or(Router::put_resource_url(filter.clone())),
    )
}
